package org.fujene.memoryblock.skippointer;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.fujene.constants.Constants;
import org.fujene.memoryblock.MemoryBlock;
import org.fujene.structure.LookupPointer;
import org.fujene.toolbox.ByteConverter;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.RawType;

public class SkipPointerBlock extends MemoryBlock{
	public static final int OPT_LEN = 0;
	public static final int OPT_NBLOCK = 1;
	public static final int OPT_TYPE = 2;
	public static final int OPT_SUBTYPE = 3;
	public static final int OPTSIZE = 4;

	public SkipPointerBlock(ByteBuffer body, int bodySize, int ID, int repBlock){
		super(body, bodySize, ID, Constants.MEMORY_TYPE_SKIPPTR, repBlock);
	}

	public SkipPointerBlock(ByteBuffer existingBody){
		super(existingBody);
	}

	public void setType(int type){
		option[OPT_TYPE] = type;
	}

	public int getIndexType(){
		return option[OPT_TYPE];
	}

	// Use repeatedly to skip pointer
	public byte[] movePointerRep(byte[] moveval){
		if(option[OPT_NBLOCK] != -1 || (option[OPT_NBLOCK] == -1 && this.getLeft() == 0)){
			// output last order's pointer that moves to next block
			int moveLength = (option[OPT_LEN] - 1) * PTR;
			byte[] lastValArray = read(moveLength, PTR);
			move(0, moveLength, PTR);
			store(moveval, 0);
			return lastValArray;
		}else{
			bodyVacancy -= PTR;
			move(0, option[OPT_LEN] * PTR, PTR);
			store(moveval, 0);
			option[OPT_LEN]++;
			return null;
		}
	}

	private byte[] movePointerFirst(int index, int from, byte[] val){
		if(option[OPT_NBLOCK] != -1 || (option[OPT_NBLOCK] == -1 && this.getLeft() == 0)){
			// output last order's pointer that moves to next block
			byte[] lastValArray = read((option[OPT_LEN] - 1) * PTR, PTR);
			move(from, (option[OPT_LEN] - index - 1) * PTR, PTR);
			store(val, from);
			return lastValArray;
		}else{
			bodyVacancy -= PTR;
			move(from, (option[OPT_LEN] - index) * PTR, PTR);
			store(val, from);
			option[OPT_LEN]++;
			return null;
		}
	}

	public byte[] setPointer(int index, LookupPointer dictPtr, boolean isModify){
		if(index < option[OPT_LEN] && index >= 0){
			int from = index * PTR;
			if(!isModify){
				// insert
				return movePointerFirst(index, from, dictPtr.toBytes());
			}else{
				if(option[OPT_LEN] == 0) bodyVacancy -= PTR;
				// modify
				setPointer(from, PTR, dictPtr);
				if(option[OPT_LEN] == 0) option[OPT_LEN]++;
				return null;
			}
		}else{
			bodyVacancy -= PTR;
			// add
			int from = option[OPT_LEN] * PTR;
			setPointer(from, PTR, dictPtr);
			option[OPT_LEN]++;
			return null;
		}
	}

	public void setNextBlock(int next){
		option[OPT_NBLOCK] = next;
	}

	public int getNextBlock(){
		return option[OPT_NBLOCK];
	}

	public int getLength(){
		return option[OPT_LEN];
	}

	public LookupPointer getPointer(int index){
		// OutOfBounds
		if(index >= option[OPT_LEN]) return null;
		return getPointer(index * PTR, PTR).withCluster(getCluster());
	}

	public int getLeft(){
		int left = this.bodyVacancy / PTR;
		return left;
	}

	public byte[] getData() throws IOException{
		ByteArrayOutputStream stream = new ByteArrayOutputStream(this.bodySize + OPTSIZE * 4);

		stream.write(getOptionByteStream());
		stream.write(this.read(0, this.bodySize));

		return stream.toByteArray();
	}

	public void setData(byte[] array){
		option[OPT_LEN] = ByteConverter.byteToInt(array, 0, 4);
		option[OPT_NBLOCK] = ByteConverter.byteToInt(array, 4, 4);
		option[OPT_TYPE] = ByteConverter.byteToInt(array, 8, 4);

		setBody(array, 12, bodySize, 0);
	}

	@Override
	public int getOptionSize(){
		return OPTSIZE;
	}

	@Override
	public byte[] getOptionByteStream(){
		ByteArrayOutputStream stream = new ByteArrayOutputStream();

		try{
			for(int i = 0; i < OPTSIZE; i++)
				stream.write(ByteConverter.intToByte(option[i], 4));
		}catch(IOException e){}
		return stream.toByteArray();
	}

	@Override
	public int getOption(int index){
		if(index >= OPTSIZE) return -1;
		return option[index];
	}

	@Override
	public void setOption(int index, int value){
		if(index >= OPTSIZE) return;
		option[index] = value;
	}

	@Override
	public ArrayType toArrayType(){
		MessagePackObject[] objectArray = new MessagePackObject[4];
		this.setHeader();
		objectArray[0] = IntegerType.create(getID());
		objectArray[1] = IntegerType.create(getCluster());
		objectArray[2] = IntegerType.create(getType());
		objectArray[3] = IntegerType.create(getOption(OPT_TYPE));
		objectArray[4] = RawType.create(readAllByteArray());

		return ArrayType.create(objectArray);
	}

	@Override
	public String getClassName(){
		return new String("SkipPointer "
			+ (option[OPT_TYPE] == Constants.MEMORY_TYPE_SKIPPTR? "term": "content"));
	}
}
